package com.helwen.constant;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author heqiwen
 * @version 1.0
 * @since 2020/11/10
 */
public class BaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {

    private static final Map<Class,SerializableToBaseEnum> converterMap = new ConcurrentHashMap<Class,SerializableToBaseEnum>();

    public static final BaseEnumConverterFactory INSTANCE = new BaseEnumConverterFactory();

    @Override
    public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
        Class<?> enumType = getEnumType(targetType);
        SerializableToBaseEnum toBaseEnum = converterMap.get(enumType);
        if(null == toBaseEnum){
            toBaseEnum = new SerializableToBaseEnum(enumType);
            converterMap.put(enumType, toBaseEnum);
        }
        return toBaseEnum;
    }

    private static class SerializableToBaseEnum<T extends Enum & BaseEnum> implements Converter<Serializable, T> {

        private final Map<Serializable,T> enumMap;
        private final Class<T> enumType;
        public SerializableToBaseEnum(Class<T> enumType) {
            this.enumType = enumType;
            enumMap = new HashMap<>();
            for (T t : enumType.getEnumConstants()) {
                enumMap.put(String.valueOf(t.getValue()), t);
            }
        }

        @Override
        public T convert(Serializable source) {
            T t = enumMap.get(String.valueOf(source));
            if(t ==null && source instanceof String){
                Enum.valueOf(enumType,String.valueOf(source));
            }
            return t;
        }
    }


    private static Class<?> getEnumType(Class<?> targetType) {
        Class<?> enumType = targetType;
        while (enumType != null && !enumType.isEnum()) {
            enumType = enumType.getSuperclass();
        }
        Assert.notNull(enumType, () -> "The target type " + targetType.getName() + " does not refer to an enum");
        return enumType;
    }
}
